/*!
    \copyright  Copyright (c) 2019 - 2025 Qualcomm Technologies International, Ltd.
                All Rights Reserved.
                Qualcomm Technologies International, Ltd. Confidential and Proprietary.
    \file
    \ingroup    ui_indicator
    \brief      Source file converts System Events to corresponding Audio Prompt UI Events
                by table look-up, using a configuration table passed in by the Application.
                It then plays these Prompts when required using the Audio subsystem.
*/

#include "ui_indicator_log_level.h"
#include "ui_indicator_prompts.h"

#include "ui_inputs.h"
#include "pairing.h"
#include "ui.h"
#include "ui_indicator_private.h"
#include "ui_indicator.h"
#include "telephony_messages.h"
#include "av.h"
#include "timestamp_event.h"

#include <power_manager.h>

#include <domain_message.h>
#include <logging.h>
#include <panic.h>
#include "system_state.h"
#include <stdlib.h>
#include <math.h>
#include <vmal.h>

#include "system_clock.h"

#define DEFAULT_NO_REPEAT_DELAY         D_SEC(5)
#define PROMPT_NONE                     0xFFFF

ui_prompts_task_data_t the_prompts;

/*! User interface internal messasges */
enum ui_internal_prompt_messages
{
    /*! Message sent to clear the last prompt information (used to ignore repeated prompts) */
    UI_INTERNAL_CLEAR_LAST_PROMPT,
    /*! Message sent later when a prompt is played. Until this message is delivered
        repeat prompts will not be played */
    UI_INTERNAL_PROMPT_PLAYBACK_COMPLETED,

    /*! Message to release the Audio SS on timer expiry */
    UI_INTERNAL_RELEASE_AUDIO_RESOURCE_TIMER
};
/* Make the type used for message IDs available in debug tools */
LOGGING_PRESERVE_MESSAGE_ENUM(ui_internal_prompt_messages)

ui_prompts_task_data_t the_prompts;

static void uiPrompt_FlushQueuedLocalOnlyPrompts(void)
{
    while (MessagePendingMatch(&the_prompts.prompt_task, TRUE, uiPrompts_FindQueuedLocalPrompt) && the_prompts.next_local_prompt_in_queue != 0)
    {
        DEBUG_LOG("uiPrompt_FlushQueuedLocalOnlyPrompts discarding %d", the_prompts.next_local_prompt_in_queue);
        MessageCancelFirst(&the_prompts.prompt_task, the_prompts.next_local_prompt_in_queue);
    }
}

static inline bool uiPrompt_isNotARepeatPlay(MessageId sys_event)
{
    return sys_event != the_prompts.last_prompt_played;
}

void uiPrompts_ClearInterruptingUiNotification(void)
{
    the_prompts.interrupting_prompt = 0;
    the_prompts.interrupting_ttp = 0;
}

static void uiPrompts_ClearLastPlayedPromptData(void)
{
    the_prompts.last_rate = 0;
    the_prompts.last_format = PROMPT_FORMAT_PCM;
    the_prompts.last_played = PROMPT_NONE;
}

static void uiPrompts_HandleInternalPrompt(Task task, MessageId sys_event, Message message)
{
    UNUSED(task);
    UNUSED(message);

    if(the_prompts.debug_disable_prompts)
    {
        DEBUG_LOG("uiPrompts_HandleInternalPrompt: Ignoring request for sys_event=%u as prompt play is disabled ", sys_event);
        return;
    }

    DEBUG_LOG("uiPrompts_HandleInternalPrompt sys_event=%u", sys_event);
    uiPrompts_SchedulePromptPlay(sys_event);
}

static void uiPrompts_CheckToReleaseAudioFramework(void)
{
    DEBUG_LOG("uiPrompts_CheckToReleaseAudioFramework");

    if (MessagesPendingForTask(&the_prompts.prompt_task, NULL) == 0)
    {
        DEBUG_LOG("uiPrompts_CheckToReleaseAudioFramework releasing");  
        appPowerPerformanceProfileRequest();
        VmalOperatorFrameworkDisableMainProcessor();
        appPowerPerformanceProfileRelinquish();
    }
    else
    {
        // Prompt module still busy, defer for another message hop.
        MessageSendLater(&the_prompts.task, UI_INTERNAL_RELEASE_AUDIO_RESOURCE_TIMER, NULL, 20000);
    }
}

static void uiPrompts_ReleaseAudioFrameworkOnShutdown(void)
{
    if (MessageCancelAll(&the_prompts.task, UI_INTERNAL_RELEASE_AUDIO_RESOURCE_TIMER) != 0)
    {
        DEBUG_LOG("uiPrompts_ReleaseAudioFrameworkOnShutdown releasing");  
        appPowerPerformanceProfileRequest();
        VmalOperatorFrameworkDisableMainProcessor();
        appPowerPerformanceProfileRelinquish();
    }
}

static void uiPrompts_HandleShutdownRequest(void)
{
    int32 time = 0;
    uint16 power_off_prompt_index = 0;

    bool prompt_is_currently_playing = MessagePendingFirst(&the_prompts.task,
                                                           UI_INTERNAL_PROMPT_PLAYBACK_COMPLETED, &time);
    bool power_off_prompt_configured = uiPrompts_GetPromptIndexFromMappingTable(POWER_OFF,
                                                                                &power_off_prompt_index);

    bool mandatory_prompt_is_playing = prompt_is_currently_playing &&
                                       the_prompts.last_prompt_played != PROMPT_NONE &&
                                       uiPrompts_IsPromptMandatory(the_prompts.last_prompt_played);
    bool mandatory_power_off_prompt_reqd = power_off_prompt_configured &&
                                       uiPrompts_IsPromptMandatory(POWER_OFF);

    if (the_prompts.prompt_playback_enabled &&
        (mandatory_prompt_is_playing || mandatory_power_off_prompt_reqd))
    {
        /* Await completion of prompts that have started playing or pending power off prompt (if mandatory). */
        the_prompts.indicate_when_power_shutdown_prepared = TRUE;
        if (prompt_is_currently_playing &&
            mandatory_power_off_prompt_reqd &&
            (the_prompts.last_prompt_played != POWER_OFF))
        {
            the_prompts.prompts_remaining_till_shutdown = 2;
        }
        else
        {
            the_prompts.prompts_remaining_till_shutdown = 1;
        }
    }
    else
    {
        /* Otherwise shutdown immediately */
        uiPrompts_ReleaseAudioFrameworkOnShutdown();
        appPowerShutdownPrepareResponse(&the_prompts.task);
    }

    DEBUG_LOG("uiPrompts_HandleShutdownRequest indicate=%d remaining_prompts=%d",
              the_prompts.indicate_when_power_shutdown_prepared,
              the_prompts.prompts_remaining_till_shutdown);
}

static bool uiPrompts_IsPromptPlayableForEvent(MessageId id)
{
    bool is_prompt_configured = FALSE;
    bool is_prompt_to_be_played = FALSE;
    uint16 prompt_index = 0;
    if (uiPrompts_GetPromptIndexFromMappingTable(id, &prompt_index) ||
        uiPrompts_IsUserHandledPrompt(id))
    {
        is_prompt_configured = TRUE;
    }
    if (is_prompt_configured && (!the_prompts.indicate_when_power_shutdown_prepared || id == POWER_OFF))
    {
        is_prompt_to_be_played = TRUE;
    }
    DEBUG_LOG("uiPrompts_IsPromptPlayableForEvent MESSAGE:ui_internal_prompt_messages:0x%04x configured=%d playable=%d",
              id, is_prompt_configured, is_prompt_to_be_played);
    return is_prompt_to_be_played;
}

static void uiPrompts_HandleMessage(Task task, MessageId id, Message message)
{
    UNUSED(task);
    UNUSED(message);

    if (uiPrompts_IsPromptPlayableForEvent(id))
    {
        Task t = &the_prompts.prompt_task;
        if (!uiPrompts_IsPromptLocalOnly(id))
        {
            // Getting a synchronised prompt shall cause us to flush all local only prompts from the queue
            if (ui_IsAudioResourceLocked() && uiPrompts_IsPromptLocalOnly(the_prompts.last_played) && !uiPrompts_IsPromptMandatory(the_prompts.last_played))
            {
                UiPrompts_ForceStopPrompt();
            }
            uiPrompt_FlushQueuedLocalOnlyPrompts();
        }
        if (MessagesPendingForTask(t, NULL) < UI_PROMPTS_MAX_QUEUE_SIZE ||
            uiPrompts_IsPromptMandatory(id))
        {
            MessageSendConditionally(t, id, NULL, ui_GetAudioResourceLockAddress());
        }
        else
        {
            DEBUG_LOG("uiPrompts_HandleMessage not queuing id MESSAGE:ui_internal_prompt_messages:0x%04x", id);
        }
    }
    else if (id == UI_INTERNAL_CLEAR_LAST_PROMPT)
    {
        DEBUG_LOG("UI_INTERNAL_CLEAR_LAST_PROMPT");
        the_prompts.last_prompt_played = PROMPT_NONE;
    }
    else if (id == UI_INTERNAL_PROMPT_PLAYBACK_COMPLETED)
    {
        DEBUG_LOG("UI_INTERNAL_PROMPT_PLAYBACK_COMPLETED ind_shutdown_prepared=%d",
                  the_prompts.indicate_when_power_shutdown_prepared);

        if (the_prompts.indicate_when_power_shutdown_prepared)
        {
            the_prompts.prompts_remaining_till_shutdown -= 1;

            DEBUG_LOG("prompts_remaining_till_shutdown=%d",
                      the_prompts.prompts_remaining_till_shutdown);

            if (the_prompts.prompts_remaining_till_shutdown == 0)
            {
                the_prompts.indicate_when_power_shutdown_prepared = FALSE;
                uiPrompts_ReleaseAudioFrameworkOnShutdown();
                appPowerShutdownPrepareResponse(&the_prompts.task);
            }
        }
    }
    else if (id == UI_INTERNAL_RELEASE_AUDIO_RESOURCE_TIMER)
    {
        uiPrompts_CheckToReleaseAudioFramework();
    }
    else if (id == APP_POWER_SHUTDOWN_PREPARE_IND)
    {
        uiPrompts_HandleShutdownRequest();
    }
    else if (id == APP_POWER_SLEEP_PREPARE_IND)
    {
        appPowerSleepPrepareResponse(&the_prompts.task);
    }
    else if (id == TELEPHONY_CALL_ANSWERED || id == TELEPHONY_INCOMING_CALL_ENDED)
    {
        Ui_RaiseUiEvent(ui_indication_type_stop_prompt, TELEPHONY_INCOMING_CALL_OUT_OF_BAND_RINGTONE, 0);
        UiPrompts_StopAndCancelPrompt(TELEPHONY_INCOMING_CALL_OUT_OF_BAND_RINGTONE);
    }
    else
    {
        // Ignore message
    }
}

void uiPrompts_HandleInterruptingPrompt(void)
{
    const ui_prompt_data_t *config = uiPrompts_GetDataForPrompt(the_prompts.interrupting_prompt);
    PanicNull((void *)config);
    uiPrompts_PlayPrompt(the_prompts.interrupting_prompt, the_prompts.interrupting_ttp, config);
    uiPrompts_ClearInterruptingUiNotification();
}

bool uiPrompts_FindQueuedLocalPrompt(Task task, MessageId id, Message payload)
{
    UNUSED(payload);
    
    if (task == &the_prompts.prompt_task)
    {
        const ui_prompt_data_t *config = uiPrompts_GetDataForPrompt(id);
        if (config != NULL)
        {
            bool unsynchronised = config->local_playback_only;
            if (unsynchronised)
            {
                the_prompts.next_local_prompt_in_queue = id;
            } 
            else 
            {
                the_prompts.next_local_prompt_in_queue = 0;
            }
            return TRUE;
        }
    }
    return FALSE;
}

/*! \brief Play prompt.

    \param sys_event The system event identifying the prompt to be played.
    \param time_to_play The microsecond at which to begin mixing of this audio prompt.
    \param config The prompt configuration data for the prompt to play.
*/
void uiPrompts_PlayPrompt(MessageId sys_event, rtime_t time_to_play, const ui_prompt_data_t *config)
{
    DEBUG_LOG("uiPrompts_PlayPrompt sys_event=%d ttp=%d", sys_event, time_to_play);

    FILE_INDEX index = FileFind(FILE_ROOT, config->filename, strlen(config->filename));
    PanicFalse(index != FILE_NONE);

    DEBUG_LOG("uiPrompts_PlayPrompt FILE_INDEX=%08x format=%d rate=%d", index , config->format, config->rate);
    TimestampEvent(TIMESTAMP_EVENT_PROMPT_PLAY_INDICATION);

    ui_SetAudioResourceLock();

    UiIndicator_PromptAudioStart(time_to_play,config, index);

    the_prompts.last_rate = config->rate;
    the_prompts.last_format = config->format;
    the_prompts.last_played = sys_event;

    MessageSendConditionally(&the_prompts.task,
                                UI_INTERNAL_PROMPT_PLAYBACK_COMPLETED,
                                NULL,
                                ui_GetAudioResourceLockAddress());

    /* We should send the UI_MANDATORY_PROMPT_PLAYBACK_COMPLETED indication to the System State Manager
        when the prompt played is mandatory.
        By checking for uiPrompts_IsPromptMandatory, which checks await_indication_completion config bool
        for the prompt(intended for Power On basically), means, we can disable this behaviour for other apps,
        if needed.
    */
    if (uiPrompts_IsPromptMandatory(sys_event))
    {
        MessageSendConditionally(SystemState_GetTransitionTask(),
                                    UI_MANDATORY_PROMPT_PLAYBACK_COMPLETED,
                                    NULL,
                                    ui_GetAudioResourceLockAddress());
    }
    
    if (the_prompts.no_repeat_period_in_ms != 0 && config->requires_repeat_delay)
    {
        MessageCancelFirst(&the_prompts.task, UI_INTERNAL_CLEAR_LAST_PROMPT);
        MessageSendLater(&the_prompts.task, UI_INTERNAL_CLEAR_LAST_PROMPT, NULL,
                            the_prompts.no_repeat_period_in_ms);
        the_prompts.last_prompt_played = sys_event;
    }
}

void uiPrompts_SchedulePromptPlay(MessageId sys_event)
{
    const ui_prompt_data_t *config = uiPrompts_GetDataForPrompt(sys_event);
    PanicNull((void *)config);
    bool unsynchronised = config->local_playback_only;

    DEBUG_LOG("uiPrompts_SchedulePromptPlay synchronised=%d enabled=%d", !unsynchronised, the_prompts.prompt_playback_enabled );

    if (uiPrompt_isNotARepeatPlay(sys_event) && (!appKymeraIsTonePlaying() && !ui_IsAudioResourceLocked()))
    {
        rtime_t time_alloted_for_graph_setup = UiIndicator_PromptAudioGetGraphSetupTimeMicroseconds(config);
        rtime_t time_now = SystemClockGetTimerTime();
        rtime_t time_to_play = rtime_add(time_now, time_alloted_for_graph_setup);

        if (!unsynchronised)
        {
            time_to_play = Ui_RaiseUiEvent(ui_indication_type_audio_prompt, sys_event, time_to_play);
        }

        if (the_prompts.prompt_playback_enabled || uiPrompts_IsPromptPlayAlways(sys_event))
        {
            uiPrompts_PlayPrompt(sys_event, time_to_play, config);
        }
    }
}

void UiPrompts_PrepareForPrompt(MessageId sys_event)
{
    const ui_prompt_data_t *config = uiPrompts_GetDataForPrompt(sys_event);
    if (config != NULL)
    {
        Ui_RaiseUiEvent(ui_indication_type_prepare_for_prompt, sys_event, 0);
        Kymera_PrepareForPrompt(config->format, config->rate);
    }
}

/*! \brief brief Set/reset play_prompt flag. This is flag is used to check if prompts
  can be played or not. Application will set and reset the flag. Scenarios like earbud
  is in ear or not and etc.

    \param play_prompt If TRUE, prompt can be played, if FALSE, the prompt can not be
    played.
*/
void UiPrompts_SetPromptPlaybackEnabled(bool play_prompt)
{
    the_prompts.prompt_playback_enabled = play_prompt;
}

Task UiPrompts_GetUiPromptsTask(void)
{
    return &the_prompts.task;
}

void UiPrompts_SetNoRepeatPeriod(const Delay no_repeat_period_in_ms)
{
    the_prompts.no_repeat_period_in_ms = no_repeat_period_in_ms;
}

void UiPrompts_NotifyUiIndication(MessageId sys_event, rtime_t time_to_play)
{
    DEBUG_LOG("UiPrompts_NotifyUiIndication sys_event=%u, enabled=%d", sys_event, the_prompts.prompt_playback_enabled);
    if (the_prompts.prompt_playback_enabled)
    {
        if (ui_IsAudioResourceLocked() )
        {
            UiPrompts_ForceStopPrompt();
            uiPrompt_FlushQueuedLocalOnlyPrompts();
            the_prompts.interrupting_prompt = sys_event;
            the_prompts.interrupting_ttp = time_to_play;
        }
        else
        {
            uiPrompts_ClearInterruptingUiNotification();
            const ui_prompt_data_t *config = uiPrompts_GetDataForPrompt(sys_event);
            PanicNull((void *)config);
            uiPrompts_PlayPrompt(sys_event, time_to_play, config);
        }
    }
}

void UiPrompts_NotifyUiPrepareIndication(MessageId sys_event)
{
     const ui_prompt_data_t *config = uiPrompts_GetDataForPrompt(sys_event);
     PanicNull((void *)config);
     Kymera_PrepareForPrompt(config->format, config->rate);
}

void UiPrompts_StopAndCancelPrompt(MessageId id)
{
    MessageCancelAll(&the_prompts.prompt_task,id);
    MessageCancelAll(&the_prompts.task, id);
    if(the_prompts.last_played == id)
    {
        UiPrompts_ForceStopPrompt();
    }
}

void UiPrompts_ForceStopPrompt(void)
{
    DEBUG_LOG("UiPrompts_ForceStopPrompt");

    if (ui_IsAudioResourceLocked())
    {
        /* Prompt now stopped, clear the lock */
        ui_ClearAudioResourceLock();

        UiIndicator_PromptAudioStop();
        uiPrompts_ClearLastPlayedPromptData();
    }
}

void UiPrompts_SetAudioFrameworkRequested(void)
{
    DEBUG_LOG("uiPrompts_SetAudioFrameworkRequested");

    /* Reset the audio release timer expiry. */
    MessageCancelAll(&the_prompts.task, UI_INTERNAL_RELEASE_AUDIO_RESOURCE_TIMER);
    MessageSendLater(&the_prompts.task, UI_INTERNAL_RELEASE_AUDIO_RESOURCE_TIMER, NULL, 20000);

    appPowerPerformanceProfileRequest();
    VmalOperatorFrameworkEnableMainProcessor();
    appPowerPerformanceProfileRelinquish();
}

/*! brief Initialise Ui prompts module */
bool UiPrompts_Init(Task init_task)
{
    UNUSED(init_task);

    DEBUG_LOG("UiPrompts_Init");

    memset(&the_prompts, 0, sizeof(ui_prompts_task_data_t));

    the_prompts.last_prompt_played = PROMPT_NONE;
    the_prompts.task.handler = uiPrompts_HandleMessage;
    the_prompts.prompt_task.handler = uiPrompts_HandleInternalPrompt;
    the_prompts.no_repeat_period_in_ms = DEFAULT_NO_REPEAT_DELAY;
    the_prompts.prompt_playback_enabled = FALSE;
    the_prompts.debug_disable_prompts = FALSE;
    the_prompts.next_local_prompt_in_queue = 0;
    uiPrompts_ClearLastPlayedPromptData();
    uiPrompts_ClearInterruptingUiNotification();

    UiPrompts_AudioSpecificInit();

    return TRUE;
}
